#region File Header
//----------------------------------------------------------------
// Copyright (C) 2002 Stefan Kyntchev
// All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY 
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR 
// FITNESS FOR A PARTICULAR PURPOSE.
//----------------------------------------------------------------
#endregion File Header

namespace AirWeb.WebControls.TreeControl
{
    using System;
	using System.Collections;
	using System.Web;
	using System.Web.UI;
    using System.Web.UI.WebControls;

	public delegate bool TraverseCallback(TreeNode i_node);

    /// <summary>
    ///    Tree node represents a single node item of the tree. It contains a NodeItem that renders
    ///    the text and icon of the node. Responsible for handling of expand/collapse actions.
    ///    Delegates rendering of the node text to NodeItem using INodeItem nterface
    /// </summary>
    public class TreeNode
    {
		ArrayList m_NodeList = null;
		TreeNode m_Parent = null;
		NodeItem m_Item = null;
		TreeControl m_TreeControl = null;
		int m_NodeID = 0;
		Panel m_myPanel;
		bool m_HasChildren = false;
		int m_NodeState = -1; // Unknown state

		public static TraverseCallback m_traverseCallback;

		/// <summary>
		/// Constructs a new node
		/// </summary>
		/// <param name="i_parent">Parent node or null if this is the root node</param>
		public TreeNode(TreeNode i_parent)
		{
			// root node doesn't have a parent
			if(i_parent != null)
			{
				MyTreeControl = i_parent.MyTreeControl;
			}

			m_Parent = i_parent;

			if(MyTreeControl != null)
			{
				//Get a new ID for this node
				m_NodeID = MyTreeControl.GenereteNodeID();

				//See if we have a saved state for this node ID
				m_NodeState = MyTreeControl.GetNodeState(m_NodeID);

				if(m_NodeState == -1)
				{
					if(NodeLevel < MyTreeControl.AutoExpandLevel)
						m_NodeState = 1;
				}
			}

			// Create a new panel
			m_myPanel = new Panel();

			int indent = 0;
			int spacing = 0;

			if(MyTreeControl != null)
			{
				indent = MyTreeControl.BranchOffset;
				spacing = MyTreeControl.VertSpacing;
			}

			// Set the correct indent
			m_myPanel.Style.Add("margin-left",indent.ToString() + "px");
			m_myPanel.Style.Add("margin-top",spacing.ToString() + "px");
			m_myPanel.Wrap = false;
		}

		/// <summary>
		/// Adds a child node to the current node
		/// </summary>
		/// <param name="i_child">The child to be added</param>
		public void AddChild(TreeNode i_child)
		{
			// Lazy create the array list
			if(m_NodeList == null)
			{
				m_NodeList = new ArrayList();
			}

			// Add the node
			m_NodeList.Add(i_child);
		}

		/// <summary>
		/// Clears all shildren of this node
		/// </summary>
		public void DeleteAllChildren()
		{
			if(m_NodeList != null)
				m_NodeList.Clear();
		}

		/// <summary>
		/// Renders the node in the supplied i_context
		/// </summary>
		/// <param name="i_context">Controls colloction of the parent</param>
		public void Render(ControlCollection i_context)
		{
			// Insert our panel
			i_context.Add(m_myPanel);

			// Do the actual rendering
			RenderNodeInternal();
		}

		/// <summary>
		/// Renders the content of the node
		/// </summary>
		protected void RenderNodeInternal()
		{
			InitPanel();

			// Render the Node item. Dont render the root nodes
			if(m_Parent != null && m_Item != null)
			{
				// Render the expand or collapse button
				RenderCollapseExpand(m_myPanel.Controls);

				// Now render the contents of the item
				m_Item.Render(m_myPanel.Controls);	
			}

			// Render the rest of the child nodes if there are any and are expanded
			if(m_NodeList != null && Expanded)
			{
				foreach(TreeNode n in m_NodeList )
				{
					n.Render(m_myPanel.Controls);
				}
			}
		}

		/// <summary>
		/// Renders expand or collapse buttons or an empty image
		/// </summary>
		/// <param name="i_context">where to add the button </param>
		protected void RenderCollapseExpand(ControlCollection i_context)
		{
			if(HasChildNodes())
			{
				ImageButton i = new ImageButton();
				i.ID = "cenode_" + m_NodeID.ToString();

				if(Expanded)
				{	
					i.ImageUrl = MyTreeControl.CollapseIcon;
					i.Click += new ImageClickEventHandler(Collapse);
					i.ToolTip = "Click to Collapse";
				}
				else
				{
					i.ImageUrl = MyTreeControl.ExpandIcon;
					i.Click += new ImageClickEventHandler(Expand);
					i.ToolTip = "Click to Expand";
				}

				i.CommandArgument = m_NodeID.ToString();
				i_context.Add(i);
			}
			else
			{
				Image i = new Image();
				i.ImageUrl = MyTreeControl.EmptyIcon;
				i_context.Add(i);
			}
		}

		/// <summary>
		/// Returns true if this node has child nodes
		/// </summary>
		/// <returns></returns>
		protected virtual bool HasChildNodes()
		{
			if(m_HasChildren || (m_NodeList != null && m_NodeList.Count != 0))
				return true;
			else
				return false;
		}

		/// <summary>
		/// Allows you to set if the item has children
		/// this is used if we retrirve children on demand
		/// </summary>
		public bool HasChildren
		{
			get { return m_HasChildren; }
			set { m_HasChildren = value; }
		}

		/// <summary>
		/// Handler for the expand event
		/// </summary>
		/// <param name="sender"> </param>
		/// <param name="e"> </param>
		public void Expand (object sender, ImageClickEventArgs e)
		{
			Expanded = true;

			//Dynamically Add children
			MyTreeControl.TreeModel.AddChildNodes(this);
			//Make sure our IDs are sequential. This will allow
			//correct event handlers to be invoked on postback
			MyTreeControl.NormalizeChildIds();

			//Recreate controls wuth the new Ids
			MyTreeControl.Controls.Clear();

			RootNode.Render(MyTreeControl.Controls);			
		}

		/// <summary>
		/// Hangler for the collapse event
		/// </summary>
		/// <param name="sender"> </param>
		/// <param name="e"> </param>
		public void Collapse (object sender, ImageClickEventArgs e)
		{
			Expanded = false;

			//Clear the cild list
			m_NodeList = null;
				
			//Make sure our IDs are sequential. This will allow
			//correct event handlers to be invoked on postback
			MyTreeControl.NormalizeChildIds();

			//Recreate controls with the new Ids
			MyTreeControl.Controls.Clear();

			RootNode.Render(MyTreeControl.Controls);
		}

		//Initializes the panel with the correct offset based on the browser
		protected void InitPanel()
		{
			m_myPanel.Controls.Clear();

			HttpBrowserCapabilities c = HttpContext.Current.Request.Browser;
			if(c.Platform == "WinCE")
			{			
				for(int i = 0; i != this.NodeLevel*2; i++)
				{
					Literal l = new Literal();
					l.Text = "&nbsp;";
					m_myPanel.Controls.Add(l);
				}
			}
		}


		static public bool Traverse(TreeNode i_root)
		{
			if(i_root.m_NodeList == null)
				return false;

			foreach(TreeNode t in i_root.m_NodeList)
			{
				m_traverseCallback(t);
				TreeNode.Traverse(t);
			}

			return false;
		}

		/// <summary>
		/// True if the node is currently expanded. Uses the State of the parent 
		/// Tree control to store the state
		/// </summary>
 		public bool Expanded
		{
			get
			{
				return m_NodeState == 1;
			}
			set
			{
				if(value)
				{
					m_NodeState = 1;
				}
				else
				{
					m_NodeState = 0;
				}
			}
		}

		/// <summary>
		/// This is the indent of the node relative to the root node
		/// </summary>
		protected int NodeLevel
		{
			get
			{
				int i = 0;
				TreeNode n = this;
				while(n.m_Parent != null)
				{
					n = n.m_Parent;
					i++;
				}

				return i;
			}
		}

		/// <summary>
		/// Returns the top most node of the tree
		/// </summary>
		public TreeNode RootNode
		{
			get
			{
				TreeNode n = this;
				while(n.m_Parent != null)
				{
					n = n.m_Parent;
				}

				return n;
			}
		}

		/// <summary>
		/// The tree control that contains this item
		/// </summary>
		public TreeControl MyTreeControl
		{
			get
			{
				return m_TreeControl;
			}
			set
			{
				m_TreeControl = value;
			}
		}

		/// <summary>
		/// This is the real item to be rendered by the tree
		/// </summary>
		public NodeItem nodeItem
		{
			get
			{
				return m_Item;
			}
			set
			{
				m_Item = value;
				m_Item.Owner = this;
			}
		}

		/// <summary>
		/// This is the real item to be rendered by the tree
		/// </summary>
		public TreeNode ParentNode
		{
			get
			{
				return m_Parent;
			}
		}

		/// <summary>
		/// Unique ID of this node
		/// </summary>
		public int NodeID
		{
			get
			{
				return m_NodeID;
			}
		}

		/// <summary>
		/// Returns the number of all children and subchildren of this node
		/// </summary>
		public int NodeCount
		{
			get
			{
				if(m_NodeList == null)
					return 0;

				int count = m_NodeList.Count;

				foreach(TreeNode t in m_NodeList)
				{
					count += t.NodeCount;
				}

				return count;
			}
		}

		/// <summary>
		/// Reassign sequential node IDs to all children
		/// </summary>
		public void NormalizeChildIds()
		{
			m_NodeID = MyTreeControl.GenereteNodeID();
			if(m_NodeList != null)
			{
				foreach(TreeNode tn in m_NodeList)
				{
					tn.NormalizeChildIds();
				}
			}
		}

		/// <summary>
		/// Get the state (expanded/collapsed) of each child
		/// </summary>
		public void  GetControlState(Hashtable i_ctrlState)
		{
			if(Expanded)
				i_ctrlState.Add(m_NodeID,1);

			if(m_NodeList != null)
			{
				foreach(TreeNode tn in m_NodeList)
				{
					tn.GetControlState(i_ctrlState);
				}
			}
		}
    }
}
